/**
 * Created by jianglinjie on Fri Sep 22 2017
 * @flow
 */
'use strict';

import type { 
  LoginParam, 
  LoginResult, 
  TokenPayload,
  LogoutParam,
  LogoutResult,
} from './types';
import strategyMap from './strategy/strategyMap';
import { createError } from '../../utils/ErrorUtils';
import { ErrorCode } from '../../constants';
import jwt from 'jsonwebtoken';
import ms from 'ms';
import fs from 'fs';
import config from '../../config';
import { logger } from '../../utils/LoggerUtils';
import { User, InvalidToken } from '../../db/models';
import { isMongoDuplicateKeyError } from '../../utils/ErrorUtils';

let RSA_PRIVATE_KEY;
let RSA_PUBLIC_KEY

try {
  RSA_PRIVATE_KEY = fs.readFileSync(config.path.rsaPrivateKeyPath);
  RSA_PUBLIC_KEY = fs.readFileSync(config.path.rsaPublicKeyPath);
} catch(err) {
  logger.error('read rsa key error ', err);
  RSA_PRIVATE_KEY = null;
  RSA_PUBLIC_KEY = null;
}

class AuthService {
  async login(type: string, param: LoginParam): Promise<LoginResult> {
    let strategy = strategyMap.get(type);
    if (strategy) {
      let userInfo = await strategy.login(param);
      let token = await this.generateToken(userInfo.uid);
      return {
        token: token,
        expireAt: Date.now() + ms(config.token.expireTime) - 60 * 1000,//考虑网络延时，客户端过期时间提早一分钟
        userInfo: userInfo,
      };
    } else {
      throw createError(400, '不支持的登录类型', ErrorCode.UNSUPPORT_LOGIN_TYPE);
    }
  }

  async parseToken(token: string): Promise<TokenPayload> {
    if (!RSA_PUBLIC_KEY) {
      throw createError(500, 'Internal Server Error', ErrorCode.COMMON_ERROR);      
    }

    try {
      //验证token是否在黑名单中
      let invalidTokenDoc = await InvalidToken.findOne({ token: token });
      if (invalidTokenDoc) {
        //401是认证失败,即登录失败
        //403是授权失败，即拒绝访问 一般是登录成功 但是没有权限访问和操作资源
        throw createError(401, 'token无效', ErrorCode.TOKEN_INVALID);                
      }

      let payload = jwt.verify(token, RSA_PUBLIC_KEY, { algorithms: ['RS256'] });
      let userDoc = await User.findById(payload.uid);
      if (!userDoc) {
        throw createError(401, '用户不存在', ErrorCode.TOKEN_INVALID);
      }
      
      if (userDoc.pwdModifyTime.getTime() > payload.signTime) {
        //修改密码时间大于token签发时间
        throw createError(401, 'token无效', ErrorCode.TOKEN_INVALID);        
      }
            
      return payload;
    } catch (err) {
      if (err.name === 'TokenExpiredError') {
        throw createError(401, 'token过期', ErrorCode.TOKEN_EXPIRE);        
      } else {
        throw createError(401, 'token无效', ErrorCode.TOKEN_INVALID);
      }
    }
  }

  async generateToken(uid: string): Promise<string> {
    if (!RSA_PRIVATE_KEY) {
      throw createError(500, 'Internal Server Error', ErrorCode.COMMON_ERROR);      
    }

    let token = jwt.sign({ uid, signTime: Date.now() }, RSA_PRIVATE_KEY, { algorithm: 'RS256', expiresIn: config.token.expireTime });
    return token;
  }

  async logout(param: LogoutParam): Promise<LogoutResult> {
    if (!param.token) {
      throw createError(400, '缺少token', ErrorCode.REQUIRE_TOKEN);
    }

    try {
      let payload = await this.parseToken(param.token);
      await InvalidToken.create({
        token: param.token,
        expireAt: payload.exp * 1000
      });

      return {
        result: true
      };
    } catch (err) {
      if (isMongoDuplicateKeyError(err)) {
        throw createError(400, '用户已登出', ErrorCode.USER_ALREADY_LOGOUT);
      }

      throw createError(400, 'token无效', ErrorCode.TOKEN_INVALID);
    }
  }
}

export default new AuthService();
